import os
import sys

from gurobipy import *
from Util.util import *
from ALNS import _calculate_and_package_metrics
from Util.load_data import *
from pathlib import Path

# 获取当前脚本的绝对路径
current_script_path = Path(__file__).resolve()
project_root = current_script_path.parent.parent
sys.path.insert(0, str(project_root))
from vrpsc.Util.Solution import Solution
from vrpsc.Util.tensorUtil import parse_vehicle_routes
from utils.data_utils import generate_hca_data, read_excel_to_tensor, write_tensor_to_excel, collect_result_to_excel, fileName_increase

LARGE = 10000


def buildModel(instance_name,time_limit):
    logging.info(f"---------------------------- {instance_name} start ----------------------------")
    logging.info(f"instance: {instance_name}")
    print(f"---------------------------- {instance_name} start ----------------------------")
    print(f"instance: {instance_name}")
    instance = read_excel(instance_name)
    x_ijr_index = {}  # edge
    t_i_d_index = {}  # 任务i的解耦合时间
    t_i_e_index = {}  # 任务i的结束时间
    t_idle_parent_i = {}  # 执行第i个任务母车的idle时间
    t_idle_child_i = {}  # 执行第i个任务子车的idle时间
    t_tardiness_i = {}   # 第i个任务的迟到时间

    N = len(instance)  # 任务的数量

    # 构建距离矩阵
    distance_map = {}
    for i_plus in range(N + 1):
        for i_minus in range(N + 1):
            for r_plus in Config.M_parent_list:
                for r_minus in Config.M_child_list:
                    for j in range(1, N + 1):
                        if i_plus == 0:
                            i_plus_position = Config.M_position_map[r_plus]
                        else:
                            i_plus_position = [instance[i_plus - 1][3], instance[i_plus - 1][4]]
                        if i_minus == 0:
                            i_minus_position = Config.M_position_map[r_minus]
                        else:
                            i_minus_position = [instance[i_minus - 1][3], instance[i_minus - 1][4]]
                        j_s_position = [instance[j - 1][1], instance[j - 1][2]]
                        j_d_position = [instance[j - 1][3], instance[j - 1][4]]
                        distance_map[i_plus, i_minus, r_plus, r_minus, j] = (
                                  get_distance(i_plus_position, j_s_position)  # 母车接子车
                                + get_distance(i_minus_position, j_s_position)  # 子母车取货
                                + get_distance(j_s_position, j_d_position))  # 子母车送货

    temp_idle_i_j_r1_r2_j_parent = {}  # 求idle时间中，中间变量
    temp_idle_i_j_r1_r2_j_child = {}

    for i in range(0, N + 1):
        t_i_d_index[i] = 0
        t_i_e_index[i] = 0
        t_idle_parent_i[i] = 0
        t_idle_child_i[i] = 0
        t_tardiness_i[i] = 0

    for i in range(N + 1):
        for j in range(N + 1):
            for r in range(1, Config.M + 1):
                x_ijr_index[i, j, r] = 0

    for i in range(N + 1):
        for j in range(N + 1):
            for r1 in Config.M_parent_list:
                for r2 in Config.M_child_list:
                    for j_ in range(1, N + 1):
                        temp_idle_i_j_r1_r2_j_parent[i, j, r1, r2, j_] = 0
                        temp_idle_i_j_r1_r2_j_child[i, j, r1, r2, j_] = 0

    model = Model()
    # model.Params.TimeLimit = time_limit if time_limit else 60
    x = model.addVars(x_ijr_index.keys(), vtype=GRB.BINARY, name='x')
    t_d = model.addVars(t_i_d_index.keys(), vtype=GRB.CONTINUOUS, name='t_d')
    t_e = model.addVars(t_i_e_index.keys(), vtype=GRB.CONTINUOUS, name='t_e')
    t_idle_parent = model.addVars(t_idle_parent_i.keys(), vtype=GRB.CONTINUOUS, name='t_idle_parent')
    t_idle_child = model.addVars(t_idle_child_i.keys(), vtype=GRB.CONTINUOUS, name='t_idle_child')
    temp_idle_parent = model.addVars(temp_idle_i_j_r1_r2_j_parent.keys(), vtype=GRB.CONTINUOUS, name='temp_idle_parent')
    temp_idle_child = model.addVars(temp_idle_i_j_r1_r2_j_child.keys(), vtype=GRB.CONTINUOUS, name='temp_idle_child')
    t_tardiness = model.addVars(t_tardiness_i.keys(), vtype=GRB.CONTINUOUS, name='t_tardiness')

    '''
        当前优化目标为，最小化最长完工时间
        '''
    # t_e_max = model.addVar(vtype=GRB.CONTINUOUS, name='t_e_max')

    distance = model.addVar(vtype=GRB.CONTINUOUS, name='distance')
    tardiness = model.addVar(vtype=GRB.CONTINUOUS, name='tardiness')
    model.addConstr(distance == quicksum(
        x[i_plus, j, r_plus] * x[i_minus, j, r_minus] * distance_map[i_plus, i_minus, r_plus, r_minus, j] for i_plus in
        range(N + 1) for i_minus in range(N + 1) for j in range(1, N + 1) for r_plus in Config.M_parent_list for r_minus in
        Config.M_child_list))

    for i in range(1, N + 1):
        model.addConstr(t_tardiness[i] >= 0)
        model.addConstr(t_tardiness[i] >= t_e[i] - instance[i - 1][5])

    model.addConstr(tardiness == quicksum(t_tardiness[i] for i in range(1, N + 1)))

    # model.addConstrs((t_e_max >= t_e[i] for i in t_i_e_index.keys()), name='t_e_max_constr')
    # 设置目标函数，最小化 t_e_max
    model.setObjective(distance * Config.weight + tardiness * (1 - Config.weight), GRB.MINIMIZE)

    '''x_ii == 0'''
    for i in range(0, N + 1):
        for j in range(0, N + 1):
            for r in range(1, Config.M + 1):
                if i == j:
                    model.addConstr(x[i, j, r] == 0, f'c0_{i}_{j}_{r}')

    '''每个任务有入，有出'''
    for j in range(1, N + 1):
        model.addConstr(quicksum(x[i, j, r] for r in Config.M_parent_list for i in range(0, N + 1)) == 1, f'c1+_{j}')

    for j in range(1, N + 1):
        model.addConstr(quicksum(x[i, j, r] for r in Config.M_child_list for i in range(0, N + 1)) == 1, f'c1-_{j}')

    for i in range(1, N + 1):
        model.addConstr(quicksum(x[i, j, r] for r in Config.M_parent_list for j in range(0, N + 1)) == 1, f'c2-_{j}')

    for i in range(1, N + 1):
        model.addConstr(quicksum(x[i, j, r] for r in Config.M_child_list for j in range(0, N + 1)) == 1, f'c2-_{j}')

    '''有入必有出'''
    for j in range(1, N + 1):
        for r in range(1, Config.M + 1):
            model.addConstr(
                (quicksum(x[i, j, r] for i in range(0, N + 1)) - quicksum(x[j, i, r] for i in range(0, N + 1))) == 0,
                f'c3-_{j}_{r}')

    '''从0开始，到0结束'''
    for r in range(1, Config.M + 1):
        model.addConstr(quicksum(x[i, 0, r] for i in range(1, N + 1)) == quicksum(x[0, i, r] for i in range(1, N + 1)),
                        f'c4-_{r}')

    '''保证一个AGV不会多次出发'''
    for r in range(1, Config.M + 1):
        model.addConstr(quicksum(x[i, 0, r] for i in range(1, N + 1)) <= 1,
                        f'c5-_{r}')

    for r in range(1, Config.M + 1):
        model.addConstr(quicksum(x[0, i, r] for i in range(1, N + 1)) <= 1,
                        f'c5-_{r}')

    '''第0个虚拟任务的起始时间为0'''
    model.addConstr(t_d[0] == 0, 'c_t_d_0')
    model.addConstr(t_e[0] == 0, 'c_t_e_0')

    for i_plus in range(0, N + 1):
        for i_minus in range(0, N + 1):
            for r_plus in Config.M_parent_list:
                for r_minus in Config.M_child_list:
                    for j in range(1, N + 1):
                        if i_plus == 0:
                            i_plus_position = Config.M_position_map[r_plus]
                        else:
                            i_plus_position = [instance[i_plus - 1][3], instance[i_plus - 1][4]]
                        if i_minus == 0:
                            i_minus_position = Config.M_position_map[r_minus]
                        else:
                            i_minus_position = [instance[i_minus - 1][3], instance[i_minus - 1][4]]
                        j_s_position = [instance[j - 1][1], instance[j - 1][2]]
                        j_d_position = [instance[j - 1][3], instance[j - 1][4]]
                        '''距离'''
                        t_i_plus2i_minus = get_distance(i_plus_position, i_minus_position) / Config.V
                        t_i_minus2j_s = get_distance(i_minus_position, j_s_position) / Config.V
                        t_j_s2j_d = get_distance(j_s_position, j_d_position) / Config.V

                        '''idle时间中间变量约束'''
                        model.addConstr(temp_idle_parent[i_minus, i_plus, r_plus, r_minus, j] >= t_e[i_minus] - t_d[
                            i_plus] - t_i_plus2i_minus - LARGE * (2 - x[i_plus, j, r_plus] - x[i_minus, j, r_minus]),
                                        f'c_temp_idle_parent_{i_minus}_{i_plus}_{r_plus}_{r_minus}_{j}')

                        model.addConstr(
                            temp_idle_child[i_minus, i_plus, r_plus, r_minus, j] >= t_d[i_plus] + t_i_plus2i_minus -
                            t_e[i_minus] - LARGE * (2 - x[i_plus, j, r_plus] - x[i_minus, j, r_minus]),
                            f'c_temp_idle_child_{i_minus}_{i_plus}_{r_plus}_{r_minus}_{j}')

                        '''得到空闲时间'''
                        model.addGenConstrMax(t_idle_parent[j], [temp_idle_parent[i_minus, i_plus, r_plus, r_minus, j]],
                                              0)
                        model.addGenConstrMax(t_idle_child[j], [temp_idle_child[i_minus, i_plus, r_plus, r_minus, j]],
                                              0)

                        t_operation_j = instance[j - 1][6]

                        # '''相邻任务的decoupling 和 ending的时间约束'''
                        # model.addConstr(
                        #     t_d[i_plus] + t_idle_parent[
                        #         j] + t_i_plus2i_minus + T_couple + t_i_minus2j_s + T_load + t_j_s2j_d + T_decouple - LARGE * (
                        #             2 - x[i_plus, j, r_plus] - x[i_minus, j, r_minus]) <= t_d[j],
                        #     f"c_p_+{i_plus}_-{i_minus}_r+{r_plus}_r-{r_minus}_{j}")
                        model.addConstr(
                            t_e[i_minus] + t_idle_child[
                                j] + Config.T_couple + t_i_minus2j_s + Config.T_load + t_j_s2j_d + Config.T_decouple + t_operation_j - LARGE * (
                                    2 - x[i_plus, j, r_plus] - x[i_minus, j, r_minus]) <= t_e[j],
                            f"c_c_+{i_plus}_-{i_minus}_r+{r_plus}_r-{r_minus}_{j}")

    '''同一个任务decoupling 和 ending的时间约束'''
    for i in range(1, N + 1):
        model.addConstr(t_e[i] == t_d[i] + instance[i - 1][6])

    # model.setParam("TimeLimit", 60)
    model.setParam("LogFile", "gurobi_log.txt")
    model.setParam("NodefileStart", 5)

    model.optimize()


    logging.info(f"---------------------------- {instance_name} end ----------------------------")
    logging.info(f"instance: {instance_name}")
    print(f"---------------------------- {instance_name} end ----------------------------")
    print(f"instance: {instance_name}")
    objective = model.ObjVal
    logging.info(f"objective: {objective}")
    sequence_map, path_init_task_map = get_path_list(x, x_ijr_index, Config.M)
    solution = Solution(instance, sequence_map, path_init_task_map)
    fitness = solution.get_fitness()
    print(f"code:{solution.get_code()}")
    print(f"path_map:{solution.get_path_map()}")
    print(f"fitness: {solution.get_fitness()}")
    print(parse_vehicle_routes(solution.get_code()))
    return solution, fitness

    # sequence_map, path_init_task_map = get_path_list(x, x_ijr_index, Config.M)
    # solution = Solution(instance, sequence_map, path_init_task_map)
    # fitness = solution.get_fitness()




def get_path_list(x, x_ijr_index, M):
    path_map = {}
    for r in range(1, M + 1):
        flag = False
        path = []
        connection_dict = {}
        for i, j, r_ in x_ijr_index:
            if r == r_:
                if round(x[i, j, r].x, 0) == 1:
                    connection_dict[i] = j
                    flag = True
        if not flag:
            # logging.info(f"route{r} connection_dict:{connection_dict}")
            logging.info(f"route{r} path:{path}")
            continue
        current = 0
        path.append(0)
        while True:
            current = connection_dict.get(current)
            if current == 0:
                break
            path.append(current)
        path.remove(0)
        # logging.info(f"route{r} connection_dict:{connection_dict}")
        logging.info(f"route{r} path:{path}")

        path_map[r] = path
    for r in range(1, Config.M + 1):
        if r not in path_map.keys():
            path_map[r] = []
    # print(f"path_map: {path_map}")
    sequence_map, path_init_task_map = path_map2sequence_map(path_map)

    return sequence_map, path_init_task_map





if __name__ == "__main__":
    # init_optimal_solution()
    # instance = read_excel("T10_I1.xlsx")
    # for index in range(1, 11):
    #     instance_name = "T20_I" + str(index)
    #     buildModel(instance_name)
    # for index in range(1, 11):
    #     instance_name = "T5_I" + str(index)
    #     buildModel(instance_name)
    size = 20
    mom_size = 4
    sub_size = 8
    run_times = 3600
    current_script_path = Path(__file__).resolve()
    project_root = (current_script_path.parent.parent)

    # 确保 exp/result/{size} 文件夹存在
    write_file_path_template = Path(f"exp/result/{size}/gruobi.xlsx")
    # 构建完整的写入路径，并确保其父目录存在
    full_write_file_path = project_root / write_file_path_template
    full_write_file_path.parent.mkdir(parents=True, exist_ok=True)  # 核心修改
    write_file = fileName_increase(full_write_file_path)
    write_file = str(write_file)  # 转换为字符串，如果后续函数需要

    write_file = str(write_file)
    for i in range(20):
        excel_file_relative_path = Path("exp") / "hca" / f"{size}" / f"tasks_{size}_{i}.xlsx"
        full_excel_file_path = project_root / excel_file_relative_path
        full_excel_file_path.parent.mkdir(parents=True, exist_ok=True)  # 核心修改
        full_excel_file_path = str(full_excel_file_path)
        fileName = full_excel_file_path
        # 采集RL数据
        if os.path.exists(fileName):
            data = read_excel_to_tensor(fileName)
        else:
            data = generate_hca_data(1, size, mom_size, sub_size, False)
            # fileName = f"data/hca/{size}/tasks_{size}_{i}.xlsx"
            write_tensor_to_excel(data, fileName)
            data = read_excel_to_tensor(fileName)
        # buildModel expects a string path
        solution, fitness = buildModel(str(fileName), run_times)
        gruobi_data_package = _calculate_and_package_metrics(
            fitness,
            solution.distance,
            solution.tardiness,
            run_times  # Assuming greedy solution runtime is part of the total ALNS runtime
        )
        gruobi_data_package[0] = solution.get_code()
        idx = 'gruobi' + f'_{run_times}_{i}'
        collect_result_to_excel(write_file, idx, gruobi_data_package)
    # update_optimal_solution()

    pass
